Variables
Initializing
When initializing a variable, you specify an identifier, size, and value. This is no different than you may have done in typed/ languages as the datatype in many typed lanaguages specifies the size.
An example of four different variables being initialized as count, min, max, and amount follows.
; example of defining variables
count db 0x20 ; db for Define Byte
min dw 0x2323 ; db for Define Word (16 bits)
max dd 0x12345678 ; dd for Define Doubleword (32 bits)
amount dq 0x1122334455667788 ; dq for Define Quadword (64 bits)Accessing and Modifying the Variables
Typically modifying a variable follows a specific flow:
Move
Modify
Store
An example of doing this within a file follows.
; an example of modifying a variable named myVar
mov eax, [myVar] ; Take the value from memory and move it into eax
add eax, 5 ; Add 5 to the value
mov [myVar], eax ; Remove the value from eax and store in memoryIf you see square brackets around a variable or register, such as in the previous example, then this indicates a memory access (or lookup), or some sort of computation. Such a computed address is known as a effective address.
Examples of Brackets with Variables
If you want to load the memory address assocated with a variable into the register rax, you would not use the brackets around the variable’s identifier.
mov rax, myVar ; load the mem address associated with var into rax.If instead you want to load the value of the variable into rax, you would use the brackets around the variable’s identifier
mov rax, [myVar] ; load the **value** stored at the mem addr of var.In the reverse direction, if you wanted to store the value held by rax into memory, you would place brackets around rax, however, the arguments would be reversed since the instructions follow a format of instruction destination, source.
mov [var], rax ; means store the value of rax into memory at address associated with varExamples of Brackets with Registers
Example 1
Starting with a simple example, assume rbx holds a memory address. If we wanted to copy the value at that memory address into rax, we would do the following:
mov rax, [rbx]If we just wanted to copy the memory address held by rbx into rax, we would do the following.
mov rax, rbxExample 2
For this example, assume rbx holds a memory address. If we wanted to store the value held by rax into the memory address held by rbx we would write the following:
mov [rbx], raxExample 3
When working with registers, we frequently have computed values, or effective addresses. For this example, assume we have an array named myArray that is holding integer data. Also, let’s assume that rdi is holding an index associated with the array.
First, let’s load the memory addess that points to the start of the array into rsi.
mov rsi, myArray ; load mem loc of myArray into rsiTo access the second element of this array, we can compute it using the square brackets and addition of 4 (for 4 bytes, the size of an int).
mov rax, [rsi + 4] ; access the second element (4-byte ints)To access some indexed element of the array where the index has been loaded into rdi, we can do a computation with the square brackets as follows. Note that we are using 4 because an int is 4 bytes and we’ve previously determined that myArray is an array of ints.
mov rax, [rsi + rdi*4] ; access some indexed element in the array (rdi holds index)Address Computation Practice
Practice 1
Given the following information about values stored in specific addresses of memory and in specific registers, determine how each of the following operands are evaluated.
Memory
| Address | Value |
|---|---|
| 0x100 | 0xFF |
| 0x104 | 0xAB |
| 0x108 | 0x13 |
| 0x10C | 0x11 |
Registers
| Register | Value |
|---|---|
| rax | 0x100 |
| rcx | 0x1 |
| rdx | 0x3 |
Compute the following.
rax[0x104]0x108[rax + 4][rax + rdx + 9][rax + rdx * 4]
Solutions 1
raxraxholds the value0x100. The solution is0x100.[0x104]The brackets indicate we need to do a memory lookup at address
0x104. The address0x104holds the value0xAB.The solution is0xAB.0x108This is an immediate value/constant. There is no lookup. Thus the solution is
0x108.[rax + 4]We take the value stored at rax and add 4 to it, then we do a memory lookup at the resulting address since this value is inside brackets.
The value at rax is
0x100. Adding 4 to this value results in 0x104. Performing a memory lookup at0x104results in0xAB.[rax + rdx + 9]We take the value stored at rax, add the value stored at rdx, and then add 9. We then perform a memory lookup since the value is in brackets.
Following this, we have
rax=0x100andrdx=0x3. The result ofrax + rdxis0x103. Adding 9 to this result yields0x10C(that is,3 + 9 = 12, and 12 is C in hex). We then perform a memory lookup at0x10Cto get0x11.[rax + rdx * 4]First multiply the value in rdx by 4. Then add the result to rax. Last perform a memory lookup.
First,
rdx * 4 = 0x3 * 4 = 12or 0xC. Thenrax + rdx = 0x100 + 0xC = 0x10C. Performing a memory lookup at0x10Cyields0x11.
A Full Program
The following code block shows us how to access a variable, modify it, and print it again. Note that this program is dependent on another program named print_rax.asm. The codeblock for print_rax.asm follows.
; variables.asm
; This file is dependent on print_rax.asm (see below)
extern print_rax
section .data
myVar dd 10 ; 32 bit value, set to 10
section .bss
temp resd 1
section .text
global _start ; tell linker where to start
_start:
; print
mov rax, [myVar] ; load the int into rax
call print_rax ; print it
; modify the variable
mov eax, [myVar] ; take the value from memory and move into eax
add eax, 5 ; add 5 to the value
mov [myVar], eax ; remove the value from eax and store in memory
; print again
mov rax, [myVar] ; load the int into rax
call print_rax ; print it
; exit
mov eax, 60
xor edi, edi ; results in 0 for status (common way of doing it)
syscall; ------------------------------------------------------------
; print_rax.asm
;
; Exports a function named `print_rax`
; Caller places an *unsigned* integer in RAX and calls print_rax
; The function prints the number in decimal followed by '\n'
; This file is for support when we want to print an integer.
; ------------------------------------------------------------
section .text
global print_rax ; allow other files to call this symbol
print_rax:
; setup
push rbp ; save old base pointer (callee-saved)
mov rbp, rsp ; establish a stack frame for this function
; Allocate a temporary buffer
; We will build the decimal string *backwards* in this buffer.
; 32 bytes is more than enough for:
; - up to 20 digits (max 64-bit unsigned)
; - 1 newline
; - safety padding
sub rsp, 32 ; move stack pointer down 32 bytes
; Prepare pointer to end of buffer,
mov rsi, rsp ; RSI = start of buffer
add rsi, 31 ; RSI = last byte in buffer (offset 31)
mov byte [rsi], 0xA ; store '\n' (ASCII 10) at end
sub rsi, 1 ; move left to where digits will be stored
; Set RCX tp 10, and we will repeatedly divide by 10 to extract the next
; more significant digit.
mov rcx, 10 ; RCX = divisor (base 10)
; If RAX == 0, then we have a special case
; and do not need to do the division loop.
test rax, rax ; if RAX == 0, then ZF is set to 0.
jnz .convert_loop ; Jump to .convert_loop if RAX != 0
mov byte [rsi], '0' ; store ASCII '0'
sub rsi, 1 ; move buffer pointer left
jmp .write_out ; skip conversion loop
.convert_loop:
; ---------------------------
; Convert number to decimal
; ---------------------------
; Repeatedly divide RAX by 10.
; Each remainder (RDX) is one digit.
; Digits are produced least-significant first.
; DIV uses RDX and RAX as the dividend.
; RDX holds the remainder.
; RAX holds the quotient.
; RAX = RAX / 10
; RDX = RAX % 10 (remainder)
mov rdx, 0 ; Zero out RDX before calling the DIV instruction
div rcx ; perform the division by 10 (RCX holds 10 right now)
add dl, '0' ; convert remainder (0–9) to ASCII
mov [rsi], dl ; store remainder char in buffer
sub rsi, 1 ; move left for next digit
test rax, rax ; check if quotient is zero
jnz .convert_loop ; if not zero, more digits remain, repeat
.write_out:
; Compute length = (buffer_end) - (start_pointer)
; buffer_end = rsp + 32
add rsi, 1 ; adjust pointer to first valid character
mov rdx, rsp ; RDX = buffer base
add rdx, 32 ; RDX = buffer end (one past last byte)
sub rdx, rsi ; RDX = number of bytes to write
; write(1, rsi, rdx)
mov eax, 1 ; syscall number 1 = sys_write
mov edi, 1 ; file descriptor 1 = stdout
; RSI = pointer to buffer
; RDX = length
syscall ; perform write
; restore rsp and rbp before returning to caller
mov rsp, rbp ; restore stack pointer
pop rbp ; restore old base pointer
ret ; return to callerTo run this example, you’ll need to assmeble both files and then use the linker to create an executable with both.
# assemble
nasm -f elf64 variables.asm -o variables.o
nasm -f elf64 print_rax.asm -o print_rax.o
# link
ld variables.o print_rax.o -o variables
# run
./variables